/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.netbeans.editor; import java.util.Map; import java.util.HashMap; import java.util.Iterator; import java.io.IOException; import java.io.Writer; import javax.swing.text.Document; import javax.swing.text.JTextComponent; import javax.swing.text.BadLocationException; /** Various services related to indentation and text formatting * are located here. Each kit can have different formatter * so the first action should be getting the right formatter by calling * Formatter.getFormatter(kitClass). * * @author Miloslav Metelka * @version 1.00 */ public class Formatter { /** * @associates Formatter */ private static Map kitFormatters = new HashMap(); /** Listener to changes in settings */ private static SettingsChangeListener settingsListener; static { settingsListener = new SettingsChangeListener() { public void settingsChange(SettingsChangeEvent evt) { String settingName = (evt != null) ? evt.getSettingName() : null; synchronized (Formatter.class) { if (Settings.FORMATTER.equals(settingName)) { kitFormatters.clear(); } else { // other settings were changed Iterator it = kitFormatters.entrySet().iterator(); while (it.hasNext()) { Map.Entry me = (Map.Entry)it.next(); Formatter f = (Formatter)me.getValue(); f.settingsChange(evt, (Class)me.getKey()); } } } } }; Settings.addSettingsChangeListener(settingsListener); } public static synchronized Formatter getFormatter(Class kitClass) { if (kitClass == null) { kitClass = BaseKit.class; } Formatter f = (Formatter)kitFormatters.get(kitClass); if (f == null) { f = (Formatter)Settings.getValue(kitClass, Settings.FORMATTER); if (f == null) { f = new Formatter(); // create basic indentation } f.settingsChange(null, kitClass); kitFormatters.put(kitClass, f); } return f; } private int tabSize; private int shiftWidth; private boolean expandTabs; private int spacesPerTab; private Acceptor hotCharAcceptor; protected void settingsChange(SettingsChangeEvent evt, Class kitClass) { String settingName = (evt != null) ? evt.getSettingName() : null; if (settingName == null || Settings.TAB_SIZE.equals(settingName)) { tabSize = SettingsUtil.getInteger(kitClass, Settings.TAB_SIZE, DefaultSettings.defaultTabSize); } // if (settingName == null || Settings.INDENT_SHIFT_WIDTH.equals(settingName)) { // depend on expand tabs etc. shiftWidth = SettingsUtil.getInteger(kitClass, Settings.INDENT_SHIFT_WIDTH, DefaultSettings.defaultShiftWidth); // } if (settingName == null || Settings.EXPAND_TABS.equals(settingName)) { expandTabs = SettingsUtil.getBoolean(kitClass, Settings.EXPAND_TABS, false); } if (settingName == null || Settings.SPACES_PER_TAB.equals(settingName)) { spacesPerTab = SettingsUtil.getInteger(kitClass, Settings.SPACES_PER_TAB, DefaultSettings.defaultSpacesPerTab); } if (settingName == null || Settings.INDENT_HOT_CHAR_ACCEPTOR.equals(settingName)) { hotCharAcceptor = SettingsUtil.getAcceptor(kitClass, Settings.INDENT_HOT_CHAR_ACCEPTOR, AcceptorFactory.FALSE); } } public int getTabSize() { return tabSize; } /** Get size of one indentation level */ public int getShiftWidth() { return shiftWidth; } public boolean expandTabs() { return expandTabs; } public int getSpacesPerTab() { return spacesPerTab; } /** Is the given character an indentation hotcharacter? */ public boolean isHotChar(char ch) { return hotCharAcceptor.accept(ch); } public char[] getIndentChars(int indent) { return Analyzer.getIndentChars(indent, expandTabs(), getTabSize()); } /** Change the indent of the given row. Document is atomically locked * during this operation. */ public void changeRowIndent(BaseDocument doc, int pos, int newIndent) throws BadLocationException { doc.atomicLock(); try { if (newIndent < 0) { newIndent = 0; } int firstNW = Utilities.getRowFirstNonWhite(doc, pos); if (firstNW == -1) { // valid first non-blank firstNW = Utilities.getRowEnd(doc, pos); } int bolPos = Utilities.getRowStart(doc, pos); doc.remove(bolPos, firstNW - bolPos); // !!! indent by spaces/tabs char[] fillBuf = getIndentChars(newIndent); doc.insertString(bolPos, new String(fillBuf), null); } finally { doc.atomicUnlock(); } } /** Increase/decrease indentation of the block of the code. Document * is atomically locked during the operation. * @param doc document to operate on * @param startPos starting line position * @param endPos ending line position * @param shiftCnt positive/negative count of shiftwidths by which indentation * should be shifted right/left */ public void changeBlockIndent(BaseDocument doc, int startPos, int endPos, int shiftCnt) throws BadLocationException { doc.atomicLock(); try { int indentDelta = shiftCnt * getShiftWidth(); if (endPos > 0 && Utilities.getRowStart(doc, endPos) == endPos) { endPos--; } int pos = startPos; for (int lineCnt = Utilities.getRowCount(doc, startPos, endPos); lineCnt > 0; lineCnt-- ) { int indent = Utilities.getRowIndent(doc, pos); if (Utilities.isRowWhite(doc, pos)) { indent = -indentDelta; // zero indentation for white line } changeRowIndent(doc, pos, Math.max(indent + indentDelta, 0)); pos = Utilities.getRowStart(doc, pos, +1); } } finally { doc.atomicUnlock(); } } /** Change row indent by the appropriate indentation engine */ public void updateRowIndent(BaseDocument doc, int pos) throws BadLocationException { int bolPos = Utilities.getRowStart(doc, pos); int indentPos = indentLine(doc, bolPos); changeRowIndent(doc, bolPos, indentPos - bolPos); } /** Update indentation of the block of the code. Document * is atomically locked during the operation. * @param doc document to operate on * @param startPos starting line position * @param endPos ending line position * @param shiftCnt positive/negative count of shiftwidths by which indentation * should be shifted right/left */ public void updateBlockIndent(BaseDocument doc, int startPos, int endPos) throws BadLocationException { doc.atomicLock(); try { Writer origWriter = new java.io.StringWriter(); Writer w = createWriter(doc, startPos, origWriter); char[] chars = doc.getChars(startPos, endPos - startPos); w.write(chars, 0, chars.length); } catch (IOException e) { e.printStackTrace(); } finally { doc.atomicUnlock(); } } public void shiftLine(BaseDocument doc, int dotPos, boolean right) throws BadLocationException { int ind = getShiftWidth(); if (!right) { ind = -ind; } if (Utilities.isRowWhite(doc, dotPos)) { ind += dotPos - Utilities.getRowStart(doc, dotPos); } else { ind += Utilities.getRowIndent(doc, dotPos); } ind = Math.max(ind, 0); changeRowIndent(doc, dotPos, ind); } /** Indents the current line. Should not affect any other * lines. * @param doc the document to work on * @param pos the pos of a character on the line * @return new pos of the original character */ public int indentLine (Document d, int pos) { if (d instanceof BaseDocument) { BaseDocument doc = (BaseDocument)d; try { int nwPos = Utilities.getRowFirstNonWhite(doc, pos); if (nwPos >= 0) { pos = nwPos; } } catch (BadLocationException e) { } } return pos; } /** Inserts new line at given position and indents the new line with * spaces. * * @param doc the document to work on * @param pos the pos of a character on the line * @return new pos to place cursor to */ public int indentNewLine (Document d, int pos) { if (d instanceof BaseDocument) { BaseDocument doc = (BaseDocument)d; boolean newLineInserted = false; doc.atomicLock(); try { doc.insertString(pos, "\n", null); // NOI18N pos++; newLineInserted = true; int indentPos = indentLine(doc, pos); changeRowIndent(doc, pos, indentPos - pos); pos = indentPos; } catch (GuardedException e) { // possibly couldn't insert additional indentation // at the begining of the guarded block // but the initial '\n' could be fine if (!newLineInserted) { java.awt.Toolkit.getDefaultToolkit().beep(); } } catch (BadLocationException e) { if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N e.printStackTrace(); } } finally { doc.atomicUnlock(); } } else { // not BaseDocument try { d.insertString (pos, "\n", null); // NOI18N pos++; } catch (BadLocationException ex) { } } return pos; } /** Creates writer that formats text that is inserted into it. * The writer should not modify the document but use the * provided writer to write to. Usually the underlaying writer * will modify the document itself and optionally it can remember * the current position in document. That is why the newly created * writer should do no buffering. * <P> * The provided document and pos are only informational, * should not be modified but only used to find correct indentation * strategy. * * @param doc document * @param pos position to begin inserts at * @param writer writer to write to * @return new writer that will format written text and pass it * into the writer */ public Writer createWriter (Document doc, int pos, Writer writer) { return writer; } } /* * Log * 16 Gandalf-post-FCS1.11.1.3 4/18/00 Miloslav Metelka shift-width refresh * 15 Gandalf-post-FCS1.11.1.2 4/6/00 Miloslav Metelka fixed getTabSize() * 14 Gandalf-post-FCS1.11.1.1 4/3/00 Miloslav Metelka undo update * 13 Gandalf-post-FCS1.11.1.0 3/8/00 Miloslav Metelka * 12 Gandalf 1.11 1/13/00 Miloslav Metelka * 11 Gandalf 1.10 1/10/00 Miloslav Metelka * 10 Gandalf 1.9 1/6/00 Miloslav Metelka * 9 Gandalf 1.8 11/14/99 Miloslav Metelka * 8 Gandalf 1.7 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 7 Gandalf 1.6 9/15/99 Miloslav Metelka * 6 Gandalf 1.5 9/10/99 Miloslav Metelka * 5 Gandalf 1.4 8/19/99 Miloslav Metelka * 4 Gandalf 1.3 8/17/99 Miloslav Metelka * 3 Gandalf 1.2 7/21/99 Miloslav Metelka * 2 Gandalf 1.1 7/20/99 Miloslav Metelka * 1 Gandalf 1.0 7/9/99 Miloslav Metelka * $ */